﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.IO;
using System.Data;
using SocialExplorer.IO.FastDBF;

namespace Soil
{
    class NSDBSoilData
    {
        public NSDBSoilData(string path, string polygonDBF, string componentDBF, string nameDBF, string layerDBF)
        {
            System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(path);
            if (!dir.Exists) return;
            _dir = path;

            System.IO.FileInfo[] fs = dir.GetFiles(polygonDBF);
            if (fs.Length == 0) return;
            _polygonDBF = fs[0].FullName;

            //find shape file
            _polygonSHP = _polygonDBF.ToLower().Replace(".dbf", ".shp");
            if (!System.IO.File.Exists(_polygonSHP))
                _polygonSHP = null;

            fs = dir.GetFiles(componentDBF);
            if (fs.Length == 0) return;
            _componentDBF = fs[0].FullName;

            fs = dir.GetFiles(nameDBF);
            if (fs.Length == 0) return;
            _nameDBF = fs[0].FullName;

            fs = dir.GetFiles(layerDBF);
            if (fs.Length == 0) return;
            _layerDBF = fs[0].FullName;
        }

        private string _dir;
        private string _polygonSHP;
        private string _polygonDBF;
        private string _componentDBF;
        private string _nameDBF;
        private string _layerDBF;

        private Dictionary<string, List<SoilLayer>> _layers = null;

        public Dictionary<string, List<SoilLayer>> Layers
        {
            get
            {
                if (_layers == null)
                {
                    var odbf = new DbfFile(Encoding.GetEncoding(1252));
                    odbf.Open(_layerDBF, FileMode.Open);

                    _layers = new Dictionary<string, List<SoilLayer>>();
                    var orec = new DbfRecord(odbf.Header);
                    for (int i = 0; i < odbf.Header.RecordCount; i++)
                    {
                        if (!odbf.Read(i, orec))
                            break;

                        SoilLayer layer = new SoilLayer(orec);
                        if (!_layers.ContainsKey(layer.SOL_ID))
                            _layers.Add(layer.SOL_ID, new List<SoilLayer>());
                        List<SoilLayer> layersForOneID = _layers[layer.SOL_ID];
                        layersForOneID.Add(layer);
                    }
                    odbf.Close();
                }
                return _layers;
            }

        }

        private Dictionary<string, Soil> _soils = null;
        private Dictionary<string, Soil> _soils_modifer_merged = null;
        private Dictionary<string, Soil> _soils_code_merged = null;
        private Dictionary<string, List<Soil>> _soils_modifer = null;
        private Dictionary<string, List<Soil>> _soils_code = null;
        
        private void readSoil()
        {
            var odbf = new DbfFile(Encoding.GetEncoding(1252));
            odbf.Open(_nameDBF, FileMode.Open);

            _soils = new Dictionary<string, Soil>();
            _soils_modifer = new Dictionary<string, List<Soil>>();
            _soils_code = new Dictionary<string, List<Soil>>();

            var orec = new DbfRecord(odbf.Header);
            for (int i = 0; i < odbf.Header.RecordCount; i++)
            {
                if (!odbf.Read(i, orec))
                    break;

                Soil soil = new Soil(orec);
                if (Layers.ContainsKey(soil.ID))
                    soil.AddLayer(Layers[soil.ID]);
                soil.AdjustNegativeDepth();
                soil.AdjustZeroAWC();
                if (soil.NLAYERS == 0)
                {
                    //set water parameters to following soil
                    if (soil.Name.ToUpper().Equals("MARSH") ||
                        soil.Name.ToUpper().Equals("UNCLASSIFIED LAND"))
                        soil.AddLayer(SoilLayer.WaterSoilLayer);
                }

                _soils.Add(soil.ID, soil);

                if (!_soils_modifer.ContainsKey(soil.ID_MODIFER))
                    _soils_modifer.Add(soil.ID_MODIFER, new List<Soil>());
                _soils_modifer[soil.ID_MODIFER].Add(soil);

                if (!_soils_code.ContainsKey(soil.ID_CODE))
                    _soils_code.Add(soil.ID_CODE, new List<Soil>());
                _soils_code[soil.ID_CODE].Add(soil);

            }
            odbf.Close();
        }

        private Dictionary<string, List<Soil>> Soils_Modifer
        {
            get
            {
                if (_soils_modifer == null) readSoil();
                return _soils_modifer;
            }            
        }

        private Dictionary<string, List<Soil>> Soils_Code
        {
            get
            {
                if (_soils_code == null) readSoil();
                return _soils_code;
            }
        }

        public Dictionary<string, Soil> Soils
        {
            get
            {
                if (_soils == null) readSoil();
                return _soils;
            }
        }

        private void getSoil(SoilComponent cmp, SoilClassifyLevel level)
        {
            if (level == SoilClassifyLevel.PROFILE && Soils.ContainsKey(cmp.SOIL_ID))                 //try soil id first
                cmp.Soil = Soils[cmp.SOIL_ID];
            else if (level == SoilClassifyLevel.PROFILE && Soils.ContainsKey(cmp.SOIL_ID.ToUpper()))  //try soil id upper case
                cmp.Soil = Soils[cmp.SOIL_ID.ToUpper()];
            else if (level <= SoilClassifyLevel.MODIFIER && _soils_modifer_merged != null && _soils_modifer_merged.ContainsKey(cmp.ID_MODIFER)) //go to modifer level
                cmp.Soil = _soils_modifer_merged[cmp.ID_MODIFER];
            else if (level <= SoilClassifyLevel.MODIFIER && _soils_modifer_merged != null && _soils_modifer_merged.ContainsKey(cmp.ID_MODIFER.ToUpper())) //go to modifer level
                cmp.Soil = _soils_modifer_merged[cmp.ID_MODIFER.ToUpper()];
            else if (level <= SoilClassifyLevel.MODIFIER && Soils_Modifer.ContainsKey(cmp.ID_MODIFER)) 
            {             
                if(_soils_modifer_merged == null)
                    _soils_modifer_merged = new Dictionary<string,Soil>();
                List<Soil> ss = Soils_Modifer[cmp.ID_MODIFER];

                Soil merged = null;
                double percent_each = 1.0 / (double)ss.Count; //assume each soil has same percentage
                double percent_merged = percent_each;
                foreach (Soil s in ss)
                {
                    if (merged == null) merged = s;
                    else
                    {
                        merged = merged.Merge(s, percent_merged, percent_each);
                        percent_merged += percent_each;
                    }
                }
                cmp.Soil = merged;
                _soils_modifer_merged.Add(cmp.ID_MODIFER, merged);
            }
            else if (level <= SoilClassifyLevel.CODE && _soils_code_merged != null && _soils_code_merged.ContainsKey(cmp.ID_CODE))
                cmp.Soil = _soils_code_merged[cmp.ID_CODE];
            else if (level <= SoilClassifyLevel.CODE && _soils_code_merged != null && _soils_code_merged.ContainsKey(cmp.ID_CODE.ToUpper()))
                cmp.Soil = _soils_code_merged[cmp.ID_CODE.ToUpper()];
            else if (level <= SoilClassifyLevel.CODE && Soils_Code.ContainsKey(cmp.ID_CODE)) //go to modifer level
            {
                if (_soils_code_merged == null)
                    _soils_code_merged = new Dictionary<string, Soil>();
                List<Soil> ss = Soils_Code[cmp.ID_CODE];

                Soil merged = null;
                double percent_each = 1.0 / (double)ss.Count; //assume each soil has same percentage
                double percent_merged = percent_each;
                foreach (Soil s in ss)
                {
                    if (merged == null) merged = s;
                    else
                    {
                        merged = merged.Merge(s, percent_merged, percent_each);
                        percent_merged += percent_each;
                    }
                }
                cmp.Soil = merged;
                _soils_code_merged.Add(cmp.ID_CODE, merged);
            }
        }

        private Dictionary<string, List<SoilComponent>> _components = null;

        public Dictionary<string, List<SoilComponent>> Components
        {
            get
            {
                if (_components == null)
                {
                    var odbf = new DbfFile(Encoding.GetEncoding(1252));
                    odbf.Open(_componentDBF, FileMode.Open);

                    StringCollection missingSoils = new StringCollection();
                    _components = new Dictionary<string, List<SoilComponent>>();
                    var orec = new DbfRecord(odbf.Header);
                    for (int i = 0; i < odbf.Header.RecordCount; i++)
                    {
                        if (!odbf.Read(i, orec))
                            break;

                        SoilComponent cmp = new SoilComponent(orec);
                        getSoil(cmp, SoilClassifyLevel.PROFILE);

                        if(cmp.Soil == null)
                        {
                            if (!missingSoils.Contains(cmp.SOIL_ID))
                                missingSoils.Add(cmp.SOIL_ID);
                            System.Diagnostics.Debug.WriteLine("No data for soil: " + cmp.SOIL_ID + " in polygon " + cmp.POLY_ID);
                        }
                        if (!_components.ContainsKey(cmp.POLY_ID))
                            _components.Add(cmp.POLY_ID, new List<SoilComponent>());
                        _components[cmp.POLY_ID].Add(cmp);
                    }
                    odbf.Close();

                    //sort
                    foreach (List<SoilComponent> cmp in _components.Values)
                        cmp.Sort(
                            delegate(SoilComponent cmp1, SoilComponent cmp2) 
                            {
                                if (cmp1.Percent == cmp2.Percent) return 0;
                                if (cmp1.Percent > cmp2.Percent) return 1;
                                return -1;
                            }
                            );

                    System.Diagnostics.Debug.WriteLine("~~~~~~~~~~~~ Missing soils ~~~~~~~~~~~~");
                    foreach (string s in missingSoils)
                        System.Diagnostics.Debug.WriteLine(s);
                }
                return _components;
            }
        }

        private Dictionary<string,SoilPolygon> _polygons = null;

        private void readPolygonsFromDBF()
        {
            if (_polygons == null)
            {
                _polygons = new Dictionary<string, SoilPolygon>();

                var odbf = new DbfFile(Encoding.GetEncoding(1252));
                odbf.Open(_polygonDBF, FileMode.Open);

                var orec = new DbfRecord(odbf.Header);
                for (int i = 0; i < odbf.Header.RecordCount; i++)
                {
                    if (!odbf.Read(i, orec))
                        break;

                    SoilPolygon polygon = new SoilPolygon(orec);
                    if (Components.ContainsKey(polygon.ID))
                        polygon.Components = Components[polygon.ID];

                    _polygons.Add(polygon.ID, polygon);
                }
                odbf.Close();
            }
        }
        private void readPolygonsFromSHP()
        {
            if (_polygons == null)
            {
                _polygons = new Dictionary<string, SoilPolygon>();

                DotSpatial.Data.IFeatureSet shp = DotSpatial.Data.Shapefile.Open(_polygonSHP);
                foreach (DotSpatial.Data.Feature fea in shp.Features)
                {
                    SoilPolygon polygon = new SoilPolygon(fea);
                    if (Components.ContainsKey(polygon.ID))
                        polygon.Components = Components[polygon.ID];

                    _polygons.Add(polygon.ID, polygon);
                }
            }
        }


        public Dictionary<string, SoilPolygon> Polygons
        {
            get
            {
                if (_polygonSHP != null) 
                    readPolygonsFromSHP();
                else 
                    readPolygonsFromDBF();
                return _polygons;
            }
        }

        private Dictionary<string, Soil> _polygonSoils = null;

        public Dictionary<string, Soil> PolygonSoils
        {
            get
            {
                if (_polygonSoils == null)
                {
                    _polygonSoils = new Dictionary<string, Soil>();
                    StringCollection  ids = new StringCollection();
                    StringCollection names = new StringCollection();
                    
                    int i = 1;
                    foreach (SoilPolygon p in Polygons.Values)
                    {
                        if (p.MergedComponent != null && p.MergedComponent.Soil != null)
                        {
                            if (!_polygonSoils.ContainsKey(p.MergedComponent.Soil.ID))
                            {
                                _polygonSoils.Add(p.MergedComponent.Soil.ID, p.MergedComponent.Soil);
                                ids.Add(p.MergedComponent.Soil.ID);
                                p.MergedComponent.Soil.LookupIndex = i; //set lookup index
                                i++;

                                //change soil name if the same name already exist
                                string name = p.MergedComponent.Soil.Name_Short;
                                if (names.IndexOf(name) >= 0)
                                {
                                    for(int j=1;j<100;j++)
                                        if (names.IndexOf(name + j.ToString()) == -1)
                                        {
                                            p.MergedComponent.Soil.ModifyName(j);
                                            names.Add(p.MergedComponent.Soil.Name_Short);
                                            break;
                                        }
                                }
                                else
                                    names.Add(p.MergedComponent.Soil.Name_Short);
                            }
                            else
                                p.MergedComponent.Soil.LookupIndex = ids.IndexOf(p.MergedComponent.Soil.ID) + 1; //different soil instance may have same id. Something could be done to avoid this.
                        }
                        else
                            System.Diagnostics.Debug.WriteLine("No information for polygon " + p.ID);
                    }
                }
                return _polygonSoils;
            }
            
        }

        public void GenerateSWATFiles()
        {
            string parameterFile = Path.Combine(_dir, "soil_parameters_usersoil.csv");
            string lookupFile = Path.Combine(_dir, "soil_lookup.csv");
            string soilFile = Path.Combine(_dir, "soil_polygon.dbf");

            //write parameters
            using (StreamWriter w = new StreamWriter(parameterFile))
            {
                w.WriteLine(Soil.SWATHeader);
                foreach (Soil s in PolygonSoils.Values)
                {
                    if (s.NLAYERS == 0)
                    {
                        if (s.Name.ToUpper().Equals("WATER") ||
                            s.Name.ToUpper().Equals("URBAN LAND")) //already in default usersoil table
                            continue;
                    }
                    w.WriteLine(s);

                }
            }

            //write lookup table
            using (StreamWriter w = new StreamWriter(lookupFile))
            {
                w.WriteLine("Value,Name");
                foreach (Soil s in PolygonSoils.Values)
                    w.WriteLine(string.Format("{0},{1}", s.LookupIndex, s.Name_Short));
            }

            //write lookup index to shapefile dbf for ArcSWAT
            var odbf = new DbfFile(Encoding.GetEncoding(1252));
            odbf.Open(soilFile, FileMode.Create);

            //add SWAT_ID column if necessary
            odbf.Header.AddColumn(new DbfColumn("POLY_ID", DbfColumn.DbfColumnType.Character,12,0));           
            odbf.Header.AddColumn(new DbfColumn("SWAT_ID", DbfColumn.DbfColumnType.Number,3,0));
            odbf.Header.AddColumn(new DbfColumn("SWAT_NAME", DbfColumn.DbfColumnType.Character, 100, 0)); //add name to do statistics
            odbf.Header.AddColumn(new DbfColumn("AREA", DbfColumn.DbfColumnType.Number, 20, 4)); 

            //set the value
            var orec = new DbfRecord(odbf.Header) { AllowDecimalTruncate = true };
            foreach (SoilPolygon poly in Polygons.Values)
            {
                orec[0] = poly.ID;
                orec[3] = poly.Area.ToString();
                orec[1] = "-1";
                orec[2] = "";
                if (poly.MergedComponent != null && poly.MergedComponent.Soil != null)
                {
                    orec[1] = poly.MergedComponent.Soil.LookupIndex.ToString();
                    orec[2] = poly.MergedComponent.Soil.Name_Short;
                }
                else
                    System.Diagnostics.Debug.WriteLine(poly.ID);
                odbf.Write(orec, true);
            }
            odbf.Close();
        }
    }
}
